﻿using System;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using SymulatorEthernetWPF.Shared;
using SymulatorEthernetWPF.UserInterface;
using SymulatorEthernetWPF.SimObjects.Layers.Physical;
using System.Windows.Media.Animation;
using SymulatorEthernetWPF.SimObjects.Wires;
using SymulatorEthernetWPF.SimObjects.Layers.DataLink;
using System.Collections.Generic;
using System.Timers;
using System.Windows.Threading;
using System.Threading;

namespace SymulatorEthernetWPF.SimObjects.Devices
{
    /// <summary>
    /// Interaction logic for NetworkInterface.xaml
    /// </summary>
    /// 
    [Serializable]
    public class NetworkInterface : ISerializable
    {
        public Guid GUID;
        public MacAdress Adress;
        public WireEnd wireEnd;
        public Rectangle Handler;
        private string _pName;
        private bool _isInColideMode=false;
        public bool isSending=false;

        private List<Ethernet> outBuffer = new List<Ethernet>();
        public Signal inBuffer;


        private int backoffs = 16;
        private bool _vEnabled = true;

        private DispatcherTimer backoffAlgorithm = new DispatcherTimer();
        private Storyboard greenBlink;
        private Storyboard yellowBlink;
        private Storyboard redBlink;

        private DispatcherTimer delay;
        



        public NetworkInterface(Rectangle handler)
        {
            GUID = Guid.NewGuid();
            Handler = handler;
        }

        public NetworkInterface(SerializationInfo info, StreamingContext context)
        {
            GUID = (Guid) info.GetValue("GUID", typeof (Guid));
            try
            {
                Adress = new MacAdress((String)info.GetValue("MAC", typeof(String)));
            }
            catch { }
            Name = (string) info.GetValue("name", typeof (String));
            IsEnabled = ((bool) info.GetValue("isEnabled", typeof (bool)));
            wireEnd = (WireEnd) info.GetValue("wireEnd", typeof (WireEnd));
            Owner = (DeviceControl) info.GetValue("owner", typeof (DeviceControl));
        }

        private void initAnimations()
        {
            DoubleAnimation deviceAnimationFadeInGreen = new DoubleAnimation();
            DoubleAnimation deviceAnimationFadeOutGreen = new DoubleAnimation();

            greenBlink = new Storyboard();
            deviceAnimationFadeInGreen.From = 1;
            deviceAnimationFadeInGreen.To = 0.7;
            deviceAnimationFadeOutGreen.BeginTime = TimeSpan.FromMilliseconds(0);
            deviceAnimationFadeInGreen.Duration = TimeSpan.FromMilliseconds(500);
            Storyboard.SetTarget(deviceAnimationFadeInGreen, Handler);
            Storyboard.SetTargetProperty(deviceAnimationFadeInGreen, new PropertyPath(Rectangle.OpacityProperty));
            greenBlink.Children.Add(deviceAnimationFadeInGreen);

            deviceAnimationFadeOutGreen.From = 0.7;
            deviceAnimationFadeOutGreen.To = 1;
            deviceAnimationFadeOutGreen.BeginTime = TimeSpan.FromMilliseconds(500);
            deviceAnimationFadeOutGreen.Duration = TimeSpan.FromMilliseconds(500);
            Storyboard.SetTarget(deviceAnimationFadeOutGreen, Handler);
            Storyboard.SetTargetProperty(deviceAnimationFadeOutGreen, new PropertyPath(Rectangle.OpacityProperty));
            greenBlink.Children.Add(deviceAnimationFadeOutGreen);      

            greenBlink.Completed += deviceControlBlink_Completed;

            DoubleAnimation deviceAnimationFadeInRed = new DoubleAnimation();
            DoubleAnimation deviceAnimationFadeOutRed = new DoubleAnimation();

            redBlink = new Storyboard();
            deviceAnimationFadeInRed.From = 1;
            deviceAnimationFadeInRed.To = 0.7;
            deviceAnimationFadeOutRed.BeginTime = TimeSpan.FromMilliseconds(0);
            deviceAnimationFadeInRed.Duration = TimeSpan.FromMilliseconds(500);
            Storyboard.SetTarget(deviceAnimationFadeInRed, Handler);
            Storyboard.SetTargetProperty(deviceAnimationFadeInRed, new PropertyPath(Rectangle.OpacityProperty));
            redBlink.Children.Add(deviceAnimationFadeInRed);

            deviceAnimationFadeOutRed.From = 0.7;
            deviceAnimationFadeOutRed.To = 1;
            deviceAnimationFadeOutRed.BeginTime = TimeSpan.FromMilliseconds(500);
            deviceAnimationFadeOutRed.Duration = TimeSpan.FromMilliseconds(500);
            Storyboard.SetTarget(deviceAnimationFadeOutRed, Handler);
            Storyboard.SetTargetProperty(deviceAnimationFadeOutRed, new PropertyPath(Rectangle.OpacityProperty));
            redBlink.Children.Add(deviceAnimationFadeOutRed);

            redBlink.Completed += deviceControlBlink_Completed;

            DoubleAnimation deviceAnimationFadeInYellow = new DoubleAnimation();
            DoubleAnimation deviceAnimationFadeOutYellow = new DoubleAnimation();

            yellowBlink = new Storyboard();
            yellowBlink.RepeatBehavior = RepeatBehavior.Forever;
            deviceAnimationFadeInYellow.From = 1;
            deviceAnimationFadeInYellow.To = 0.5;
            deviceAnimationFadeOutYellow.BeginTime = TimeSpan.FromMilliseconds(0);
            deviceAnimationFadeInYellow.Duration = TimeSpan.FromMilliseconds(1000);
            Storyboard.SetTarget(deviceAnimationFadeInYellow, Handler);
            Storyboard.SetTargetProperty(deviceAnimationFadeInYellow, new PropertyPath(Rectangle.OpacityProperty));
            yellowBlink.Children.Add(deviceAnimationFadeInYellow);

            deviceAnimationFadeOutYellow.From = 0.5;
            deviceAnimationFadeOutYellow.To = 1;
            deviceAnimationFadeOutYellow.BeginTime = TimeSpan.FromMilliseconds(1000);
            deviceAnimationFadeOutYellow.Duration = TimeSpan.FromMilliseconds(1000);
            Storyboard.SetTarget(deviceAnimationFadeOutYellow, Handler);
            Storyboard.SetTargetProperty(deviceAnimationFadeOutYellow, new PropertyPath(Rectangle.OpacityProperty));
            yellowBlink.Children.Add(deviceAnimationFadeOutYellow);
        }

        private void blinkGreen()
        {
            if (greenBlink == null)
                initAnimations();
            this.Handler.Fill = Brushes.Green;
            this.Handler.Stroke = Brushes.Green;
            greenBlink.Begin();
        }
        private void blinkRed()
        {
            if (redBlink == null)
                initAnimations();
            this.Handler.Fill = Brushes.Red;
            this.Handler.Stroke = Brushes.Red;
            redBlink.Begin();
        }
        private void blinkYellow()
        {
            if (yellowBlink == null)
                initAnimations();
            this.Handler.Fill = Brushes.Yellow;
            this.Handler.Stroke = Brushes.Yellow;
            yellowBlink.Begin();
        }
        private void blinkYellowStop()
        {
            if (yellowBlink != null)
            {
                this.Handler.Fill = Brushes.Black;
                this.Handler.Stroke = Brushes.Black;
                yellowBlink.Stop();
            }
        }

        public DeviceControl Owner { get; set; }

        public Point Position
        {
            get
            {
                if(Handler!=null)
                    return new Point((Canvas.GetLeft(Owner) + Handler.Margin.Left) + 4,
                                     (Canvas.GetTop(Owner) + Handler.Margin.Top) + 4);
                else return new Point(0, 0); 
            }
        }

        public string Name
        {
            get { return _pName; }
            set
            {
                
                _pName = value;
                if (Handler != null)
                    Handler.ToolTip = _pName;
                
            }
        }

        public bool IsEnabled
        {
            get { return _vEnabled; }
            set
            {
                if (value && Handler != null)
                {
                    Handler.Fill = Brushes.Black;
                    Handler.Stroke = Brushes.Black;
                    if (wireEnd != null) if (wireEnd.wire != null) wireEnd.wire.updateWire();
                    //Owner.logList.Add("[" + Simulation.tickCount + "](" + Name + "@" + Owner.deviceLabel.Content.ToString() + ") Interfejs aktywny");
                }
                else if (Handler != null)
                {
                    Handler.Fill = Brushes.Gray;
                    Handler.Stroke = Brushes.Gray;
                    if (wireEnd != null) if(wireEnd.wire!=null) wireEnd.wire.updateWire();
                    //Owner.logList.Add("[" + Simulation.tickCount + "](" + Name + "@" + Owner.deviceLabel.Content.ToString() + ") Interfejs nieaktywny");
                }
                _vEnabled = value;
            }
        }

        #region ISerializable Members

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("GUID", GUID);
            if(Adress!=null)
                info.AddValue("MAC", Adress.Adress);
            info.AddValue("isEnabled", IsEnabled);
            info.AddValue("name", Name);
            info.AddValue("wireEnd", wireEnd);
            info.AddValue("owner", Owner);
        }

        #endregion

        public void UserControlMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!StaticVars.IsSettingUpWire && wireEnd == null)
            {
                Simulation.UndoStack.Push(new HistoryElement());
                Simulation.RedoStack.Clear();
                var wr = new Wire(this);
                StaticVars.MainWindow.customCanvas.Children.Insert(0, wr);
                StaticVars.TmpWire = wr;
                StaticVars.IsSettingUpWire = true;
                wireEnd = StaticVars.TmpWire.wireEndA;
            }
            else if (!StaticVars.IsSettingUpWire && wireEnd != null)
            {
                StaticVars.TmpWire = wireEnd.wire;
                wireEnd.wire.addMouseEvent();
                if (wireEnd.wire.wireEndA == wireEnd)
                    wireEnd.wire.wireEndA = null;
                else
                    wireEnd.wire.wireEndB = null;
                //wireEnd.networkInterface.wireEnd = null;
                wireEnd = null;
                StaticVars.IsSettingUpWire = true;
            }
            else if (wireEnd == null)
            {
                StaticVars.TmpWire.removeMouseEvent();

                if (StaticVars.TmpWire.wireEndA == null)
                {
                    StaticVars.TmpWire.wireEndA = new WireEnd(StaticVars.TmpWire, this);
                    wireEnd = StaticVars.TmpWire.wireEndA;
                }

                if (StaticVars.TmpWire.wireEndB == null)
                {
                    StaticVars.TmpWire.wireEndB = new WireEnd(StaticVars.TmpWire, this);
                    wireEnd = StaticVars.TmpWire.wireEndB;
                }
                
                StaticVars.IsSettingUpWire = false;
                wireEnd.wire.updateWire();
            }

        }

        internal void UserControlMouseEnter(object sender, MouseEventArgs e)
        {
            StaticVars.MainWindow.statusLabel.ShowMessage(this.Name);
        }

        internal void UserControlMouseLeave(object sender, MouseEventArgs e)
        {
            StaticVars.MainWindow.statusLabel.FadeOut();
        }        



        public void sendFrame(Ethernet frame)
        {
            if (wireEnd.wire.IsUsable())
            {
                if (Owner is HubControl)
                {
                    if (wireEnd != null)
                    {
                        wireEnd.sendSignal(frame);
                    }
                }
                else
                {
                    if (inBuffer != null && !wireEnd.wire.isDuplex)
                    {
                        outBuffer.Insert(outBuffer.Count, frame);
                    }
                    else if (outBuffer.Count == 0)
                    {
                        if (wireEnd != null)
                        {
                            outBuffer.Insert(outBuffer.Count, frame);
                            wireEnd.sendSignal(frame);
                        }
                    }
                    else
                    {
                        if (wireEnd != null)
                        {
                            outBuffer.Insert(outBuffer.Count, frame);
                        }
                    }
                }
            }
        }
        
        private void resendFrameFromBuffer()
        {
            if (wireEnd != null && inBuffer == null && !backoffAlgorithm.IsEnabled && outBuffer.Count!=0 && !isSending && wireEnd.wire.IsUsable())
            {
                delay = new DispatcherTimer();
                delay.Interval = TimeSpan.FromMilliseconds(1000);
                delay.Tick += resendDelay;
                delay.Start();
            }
            
        }

        void resendDelay(object sender, EventArgs e)
        {
            ((DispatcherTimer)sender).Stop();
            if(outBuffer.Count!=0 && !isSending)
                wireEnd.sendSignal(outBuffer[0]);            
        }

        public void sendJam()
        {
            if (wireEnd != null && wireEnd.wire.IsUsable())
            {
                wireEnd.sendJam();
                blinkRed();
            }
        }

        public void abortEthernetFrame(Ethernet frame)
        {
            if (isSending && wireEnd.signals.Count>0)
            {
                for(int i=wireEnd.signals.Count-1; i>=0; i--)
                {
                    if (wireEnd.signals[i].ethernet == frame)
                        wireEnd.signals[i].stop();
                }
            }
            outBuffer.Remove(frame);

        }

        public void receiveSignalStart(Signal signal)
        {
            if (signal.isJam && !(Owner is HubControl))
            {
                wireEnd.stopSignals();
                inBuffer = signal;
            }
            else if (inBuffer != null && !(Owner is HubControl))
            {
                inBuffer.stop();
                signal.stop();
                inBuffer = null;
                Owner.logList.Add("[" + Simulation.tickCount + "](" + Name + ") Kolizja");
                Owner.receivedData.Add(new ReceivedData(null, Simulation.tickCount, FrameAction.Collided));
                if (Owner is SwitchControl)
                    Owner.abortReceiving(signal, this);
                sendJam();
            }
            else
            {
                inBuffer = signal;
                Owner.startReceiving(signal, this);
            }
        }

        public void receiveSignalEnd(Signal signal)
        {
            inBuffer = null;
            if (signal.isJam && !(Owner is HubControl))
            {
                backoffAlgorithm = new DispatcherTimer();
                backoffAlgorithm.Interval = TimeSpan.FromMilliseconds(StaticVars.randomNumberGenerator.Next(5000) + 5000);
                Owner.logList.Add("[" + Simulation.tickCount + "](" + Name +") Sygnał zagłuszający");
                Owner.logList.Add("[" + Simulation.tickCount + "](" + Name + ") Wycofanie ("+backoffAlgorithm.Interval.Seconds+")");
                backoffAlgorithm.Tick += backoffAlgorithm_Tick;
                backoffAlgorithm.Start();
                blinkYellow();
                return;
            }
            
            if (!(Owner is HubControl))
            {
                if (outBuffer.Count != 0)
                    resendFrameFromBuffer();

            }

            Owner.stopReceiving(signal, this);

            signal.source.signalReceived(signal);
            blinkGreen();
            
        }


        void backoffAlgorithm_Tick(object sender, EventArgs e)
        {
            backoffAlgorithm.Interval = TimeSpan.FromMilliseconds(StaticVars.randomNumberGenerator.Next(5000) + 20000);
            if (backoffs > 0)
            {
                if (outBuffer.Count == 0)
                {
                    backoffAlgorithm.Stop();
                    blinkYellowStop();
                    backoffs = 16;
                    return;
                }
                if (wireEnd != null && inBuffer == null && outBuffer.Count>0 && !isSending)
                {
                    wireEnd.sendSignal(outBuffer[0]);
                }
                if(!isSending)
                    backoffs--;
            }
            else
            {
                outBuffer.RemoveAt(0);
                backoffAlgorithm.Stop();
                backoffs = 16;
                if (outBuffer.Count != 0)
                    resendFrameFromBuffer();

            }
        }

        

        public void signalReceived(Signal signal)
        {
            backoffs = 16;
            Owner.logList.Add("[" + Simulation.tickCount + "](" + Name + ") Wysłano ramkę");
            backoffAlgorithm.Stop();
            blinkYellowStop();
            wireEnd.signals.Remove(signal);
            outBuffer.Remove(signal.ethernet);
            if (outBuffer.Count != 0)
            {
                resendFrameFromBuffer();
            }
        }


        void deviceControlBlink_Completed(object sender, EventArgs e)
        {
            this.Handler.Fill = Brushes.Black;
            this.Handler.Stroke = Brushes.Black;
            if (backoffAlgorithm.IsEnabled)
                blinkYellow();
        }

        internal void collissionDetected()
        {
            if (!(Owner is HubControl) && !_isInColideMode)
            {
                Owner.logList.Add("[" + Simulation.tickCount + "](" + Name + ") Kolizja");
                _isInColideMode = true;
                delay = new DispatcherTimer();
                delay.Interval=TimeSpan.FromMilliseconds(500);
                delay.Tick+=delay_Tick;                
                delay.Start();
            }
        }

        void delay_Tick(object sender, EventArgs e)
        {
            ((DispatcherTimer)sender).Stop();
            _isInColideMode = false;
            inBuffer = null; //
            for(int i = wireEnd.getOtherEnd().signals.Count-1; i>=0; i--)
            {
                wireEnd.getOtherEnd().signals[i].stop();
            }
            sendJam();
        }

        internal void reset()
        {
            if (inBuffer != null)
                inBuffer.stop();
            inBuffer = null;
            outBuffer = new List<Ethernet>();
            _isInColideMode = false;
            if(backoffAlgorithm!=null)
                backoffAlgorithm.Stop();
            blinkYellowStop();
            if (delay != null)
                delay.Stop();
            for(int i=0; i<wireEnd.signals.Count; i++)
            {
                wireEnd.signals[i] = null;
            }
            wireEnd.signals.Clear();
        }
    }
}